aidl 实现native service和App通信

您所在的位置:网站首页 android native层开发 aidl 实现native service和App通信

aidl 实现native service和App通信

2024-07-12 18:52| 来源: 网络整理| 查看: 265

之前一直写的是android应用之间的aidl开发,最近学习的是native service和App之间通过aidl进行通信,这里记录一下。主要介绍的是native service和App端aidl的实现,至于service的编译过程我这里没有详细的记录。我所用的android版本是11.

1、定义aidl文件

aidl文件中定义了我们想要实现的功能,是后续一切的开始,为了测试回调功能,我这里定义了一个回调接口和一个自定义数据类型FileInfo, 用来跨进程传递信息的自定义数据结构。

FileInfo.aidl

package com.hht; /**@hide*/ parcelable FileInfo cpp_header "FileInfo.h";

IFileInfoCallback.aidl

package com.hht; import com.hht.FileInfo; interface IFileInfoCallback{ void backFileInfo(in FileInfo fileInfo); }

IGetFileInfo.aidl

package com.hht; import com.hht.IFileInfoCallback; interface IGetFileInfo{ // @utf8InCpp注解可以使生成的头文件中String与std::string对应起来 void getFileInfo(@utf8InCpp String name); // register callback void registerCallback(IFileInfoCallback callback); }

上面我们定义了三个aidl文件,不过自定义类型FileInfo的定义并不在这里,而是在外面,它继承Parceable, 并实现writeToParcel和readFromParcel两个函数。

FileInfo.h

#ifndef FILE_INFO_H #define FILE_INFO_H #include #include #include namespace com{ namespace hht{ struct FileInfo:public android::Parcelable{ public: android::String16 name, // file name ctime; // create time int64_t fileSize; virtual android::status_t writeToParcel(android::Parcel *parcel) const override; virtual android::status_t readFromParcel(const android::Parcel* parcel) override; }; } }

FileInfo.cpp

#include "FileInfo.h" namespace com{ namespace hht { android::status_t FileInfo::writeToParcel(android::Parcel *parcel) const{ android::status_t res; if (parcel == nullptr) { return android::BAD_VALUE; } res = parcel->writeString16(name); if (res != android::OK) return res; res = parcel->writeString16(ctime); if (res != android::OK) return res; res = parcel->writeInt64(fileSize); if (res != android::OK) return res; return res; } android::status_t FileInfo::readFromParcel(const android::Parcel *parcel){ if (parcel == nullptr) { return android::BAD_VALUE; } parcel->readString16(); parcel->readString16(); parcel->readInt64(&fileSize); return android::OK; } } // namespace hht }

看一下整个工程的结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WIkY8ZuC-1642559685168)(screenshot-20220118-134720.png)]

Android.bp文件是我写来编译整个工程的,需要借助android源码才能编译

cc_library_static{ name:"libaidltest", local_include_dirs:[ "include" ], aidl:{ local_include_dirs:["aidl"], include_dirs:[ "frameworks/native/aidl/binder" ], export_aidl_headers:true, }, srcs:[ "FileInfo.cpp", ":libfile_aidl" ], shared_libs:[ "libbinder", "libutils", ], } filegroup{ name:"libfile_aidl", srcs:[ "aidl/com/hht/FileInfo.aidl", "aidl/com/hht/IFileInfoCallback.aidl", "aidl/com/hht/IGetFileInfo.aidl" ], path:"aidl", }

在该工程路径下终端输入mm命令开始编译,编译成功的话会在out目录下找到对应的头文件,具体的目录看编译完成时的提示。

2、实现aidl中定义的函数的功能

在out 目录下我们可以看到生成的Bn和Bp头文件,可以不用将其复制过来,直接引用即可,我们定义一个类来继承BnGetFileInfo,然后将生成的函数定义复制过来,文件内容如下

GetFileInfo.h

#ifndef GET_FILE_INFO_H #define GET_FILE_INFO_H #include #include #include "FileInfo.h" class MyFileService:public com::hht::BnGetFileInfo { public: MyFileService(); ~MyFileService(); ::android::sp mycallback; virtual::android::binder::Status getFileInfo(const ::std::string& name); virtual::android::binder::Status registerCallback(const ::android::sp& callback); }; #endif

在类MyFileService中,我们定义了一个IFileInfoCallback类型的成员, 它用来保存回调的对象。

GetFileInfo.cpp

#include "GetFileInfo.h" #include #include #include #include #include MyFileService::MyFileService(){ } MyFileService::~MyFileService(){ } ::android::binder::Status MyFileService::getFileInfo(const ::std::string& name){ struct stat st; // 文件不存在或者是不能读取 if (stat(name.c_str(),&st) backFileInfo(fileInfo); return ::android::binder::Status::ok(); } ::android::binder::Status MyFileService::registerCallback(const ::android::sp& callback){ mycallback = callback; return ::android::binder::Status::ok(); }

上面的函数也没什么好解释的,就是利用stat读取文件的基本信息,回调注册时将回调的对象保存起来。其实看逻辑的话我们完全没有必要注册一个回调,直接返回就可以了,我这里只是为了测试回调这个功能才这样写的。最后,我们还要将服务注册到serviceManager中,注册的函数调用如下

main.cpp

#include #include #include #include #include #include using namespace android; int main(int argc, char** argv) { sp proc(ProcessState::self()); sp sm = defaultServiceManager(); ALOGI("ServiceManager:%p",sm.get()); sm->addService(String16("MyFileService"),new MyFileService()); ProcessState::self()->startThreadPool(); IPCThreadState::self()->joinThreadPool(); return 0; }

修改Android.bp文件,生成可执行文件,我这里为了方便测试,还添加了rc文件和配置了te规则实现这个服务的开机启动,如果是为了简单的测试可以不必这样,将生成的so文件push到/system/lib和/system/lib64目录,将可执行文件myfileservice放到/system/bin目录,重启手机,再手动的执行可执行文件。push之前记得先root和remount, 否则会提示没有权限。如果你也想实现开机启动,请看我的另一篇文章。

https://editor.csdn.net/md/?articleId=122440555

打开手机shell, 通过ps查看我们的服务是否已经在执行了。

3、应用连接服务

这里我写一个简单的App测试我们的服务,首先将我们定义的aidl文件复制到工程的aidl文件夹中,需要对FileInfo.aidl做一点改变。

package com.hht; /**@hide*/ parcelable FileInfo;

我们在com.hht这个包下新建一个FileInfo类,同样需要继承Parceable接口。

package com.hht; import android.os.Parcel; import android.os.Parcelable; import androidx.annotation.NonNull; public class FileInfo implements Parcelable { public String name; public String ctime; public int fileSize; protected FileInfo(Parcel in) { name = in.readString(); ctime = in.readString(); fileSize = in.readInt(); } public static final Creator CREATOR = new Creator() { @Override public FileInfo createFromParcel(Parcel in) { return new FileInfo(in); } @Override public FileInfo[] newArray(int size) { return new FileInfo[size]; } }; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getCtime() { return ctime; } public void setCtime(String ctime) { this.ctime = ctime; } public int getFileSize() { return fileSize; } public void setFileSize(int fileSize) { this.fileSize = fileSize; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); dest.writeString(ctime); dest.writeInt(fileSize); } @NonNull @Override public String toString() { StringBuffer buffer = new StringBuffer(); buffer.append("name:"+name+"\n"); buffer.append("size:"+fileSize+"\n"); buffer.append("ctime:"+ctime+"\n"); return buffer.toString(); } }

接下来我在MainActivity中简单的连接上服务,注册回调,然后测试一下回调功能是否实现,代码很简单。

package com.hht.example1; import androidx.appcompat.app.AppCompatActivity; import android.app.Activity; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.Toast; import com.hht.FileInfo; import com.hht.IFileInfoCallback; import com.hht.IGetFileInfo; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class MainActivity extends Activity { private static final String TAG = "MainExample"; private Button button; // 这里必须是实现IFileInfoCallback.Stub(),而不是IFileInfoCallback这个接口 private IFileInfoCallback mycallback = new IFileInfoCallback.Stub() { @Override public void backFileInfo(FileInfo fileInfo) throws RemoteException { Log.d(TAG, fileInfo.toString()); } }; private IGetFileInfo getFileInfoService; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = findViewById(R.id.button); connect_service(); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { getFileInfoService.getFileInfo("/data/local/tmp/test.txt"); } catch (RemoteException e) { e.printStackTrace(); } } }); } private void connect_service() { try { Class clazz = Class.forName("android.os.ServiceManager"); Method method = clazz.getMethod("getService",String.class); IBinder binder = (IBinder) method.invoke(null,"MyFileService"); if (binder != null){ getFileInfoService = IGetFileInfo.Stub.asInterface(binder); // 千万不要忘记注册 getFileInfoService.registerCallback(mycallback); Log.d(TAG, "连接成功"); } } catch (ClassNotFoundException | NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (RemoteException e) { e.printStackTrace(); } } }

连接成功后就可以进行测试了,经验证可以实现回调,总结一下需要注意的是:

1、自定义数据结构FileInfo在两端都需要实现,都实现了Parceable接口。

2、bp文件中不需要将FileInfo.aidl加入编译。

死亡回调

有时候服务端可能因为某种原因死亡了,但是客户端不知道,这是可以在客户端写一个监听,服务死亡时就会收到Binder的通知。

try { operation.asBinder().linkToDeath(new IBinder.DeathRecipient() { @Override public void binderDied() { Log.d("binder", "服务端死亡啦~!"); } }, 0); } catch (RemoteException e) { e.printStackTrace(); } 4、aidl的一些其他知识点

oneway关键字

它用于修改远程调用的行为,对于本地调用加不加都没有区别。首先,什么是远程调用呢?它就是异步调用,如果我们调用了一个远程方法,这个方法可能耗时很长,如果我们不想等待,就可以加上这个关键字,那调用后就会立即返回不会被阻塞在这里。

interface IMedia { oneway void start();//异步,假设执行2秒 oneway void stop();//异步,假设执行2秒 int getVolume();// 同步 }

数据走向标记

aidl中支持java中所有的原语类型(即8中基本类型),此外还支持String, CharSequence, List, Map, (事实上接受的是ArrayList和HashMap), 对于非原语类型,需要加上数据走向的标记,in, out, inout, 原语类型默认是in,不能是其他的方向。

定义接口注意事项

自动生成的IBinder接口中包含了我们之间加的所有注释。可以在aidl接口中定义String常量和int类型的常量。方法调用是由transact()方法分派的,该代码通常基于接口中的方法索引,由于这会增加版本控制的难度,因此我们可以手动配置,如 void method() = 10; 使用@nullable注释可以空参数或者返回类型对于native service端定义的aidl接口,对于String类型的参数可以加上@utf8InCpp注解,生成的头文件中String类型会被自动转换成std::string

接口实现

如果要实现aidl中定义的接口,那么需要实现是它的子类Stub, 它是父类接口的抽象实现,并且会实现接口中的所有方法。Stub中还有个非常重要的方法,其中最值得注意的是asInterface()方法, 该方法会接收IBinder( 通常是传递给客户端onServiceConnected()回调的方法)方法回调的参数, 并返回Stub接口的实例。

带Bundle参数(包含Parcelable类型)的方法

如果您的 AIDL 接口包含接收Bundle作为参数(预计包含 Parcelable 类型)的方法,则在尝试从Bundle读取之前,请务必通过调用 Bundle.setClassLoader(ClassLoader) 设置Bundle的类加载器。否则,即使您在应用中正确定义 Parcelable 类型,也会遇到 ClassNotFoundException。例如,

// IRectInsideBundle.aidl package com.example.android; /** Example service interface */ interface IRectInsideBundle { /** Rect parcelable is stored in the bundle with key "rect" */ void saveRect(in Bundle bundle); }

如下方实现所示,在读取 Rect 之前,ClassLoader 已在 Bundle 中完成显式设置

private final IRectInsideBundle.Stub binder = new IRectInsideBundle.Stub() { public void saveRect(Bundle bundle){ bundle.setClassLoader(getClass().getClassLoader()); Rect rect = bundle.getParcelable("rect"); process(rect); // Do more with the parcelable. } };


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3